﻿// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号：程序员速成 ，内含一辈子都让你感激自己的优质视频教程，欢迎关注

#include <iostream>
#include <vector>

#ifdef _DEBUG   //只在Debug（调试）模式下
#ifndef DEBUG_NEW
#define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
#define new DEBUG_NEW
#endif
#endif

//#include <boost/type_index.hpp>
using namespace std;
//#pragma warning(disable : 4996) 

namespace _nmsp1
{	
	//玩家主角相关的备忘录类
	class FighterMemento
	{
	private:
		//构造函数，用private修饰以防止在外部被随意创建
		FighterMemento(int life,int magic,int attack):m_life(life), m_magic(magic), m_attack(attack) {}

	private:
		//提供一些供Fighter类访问的接口，用private修饰防止被任意类访问
		friend class Fighter; //友元类Fighter可以访问奔雷的私有成员函数
		int getLife() const { return m_life; }
		void setLife(int life) { m_life = life; }

		int getMagic() const { return m_magic; }
		void setMagic(int magic) { m_magic = magic; }

		int geAttack() const { return m_attack; }
		void setAttack(int attack) { m_attack = attack; }


	private:
		//玩家主角类中要保存起来的数据，就放到这里来
		int m_life; //生命值
		int m_magic; //魔法值
		int m_attack; //攻击力
	};

	//玩家主角类
	class Fighter
	{
	public:
		//构造函数
		Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}

	public:
		//将玩家数据写入备忘录（创建备忘录，并在其中存储了当前状态）
		FighterMemento* createMomento()
		{
			return new FighterMemento(m_life, m_magic, m_attack);
		}
		//从备忘录中恢复玩家数据
		void restoreMomento(FighterMemento* pfm)
		{
			m_life = pfm->getLife();
			m_magic = pfm->getMagic();
			m_attack = pfm->geAttack();
		}

		//为测试目的引入的接口，设置玩家的生命值为0（玩家死亡）
		void setToDead()
		{
			m_life = 0;
		}
		//用于输出一些信息
		void displayInfo()
		{
			cout << "玩家主角当前的生命值、魔法值、攻击力分别为：" << m_life << "," << m_magic << "," << m_attack << endl;
		}

	private:
		//角色属性
		int m_life; //生命值
		int m_magic; //魔法值
		int m_attack; //攻击力
		//.....其他数据略
	};

	//管理者（负责人）类
	class FCareTaker
	{
	public:
		//构造函数
		FCareTaker(FighterMemento* ptmpfm) :m_pfm(ptmpfm) {} //形参是指向备忘录对象的指针

		//获取指向备忘录对象的指针
		FighterMemento* getMemento()
		{
			return m_pfm;
		}
		//保存指向备忘录对象的指针
		void setMemento(FighterMemento *ptmpfm)
		{
			m_pfm = ptmpfm;
		}

	private:
		FighterMemento* m_pfm; //指向备忘录对象的指针
	};

	//------------------------
	//支持多个快照的负责人（管理者）类
	class FCareTaker2
	{
	public:
		//析构函数用于释放资源
		~FCareTaker2()
		{
			for (auto iter = m_pfmContainer.begin(); iter != m_pfmContainer.end(); ++iter)
			{
				delete (*iter);
			}
		}

		//保存指向备忘录对象的指针
		void setMemento(FighterMemento* ptmpfm)
		{
			m_pfmContainer.push_back(ptmpfm);
		}

		//获取指向备忘录对象的指针
		FighterMemento* getMemento(int index)
		{
			auto iter = m_pfmContainer.begin();
			for (int i = 0; i <= index; ++i)
			{
				if (i == index)
					return (*iter);
				else
					++iter;
			}
			return NULL;
		}

	private:
		//存储备忘录对象指针的容器
		vector<FighterMemento*> m_pfmContainer; 
	};
	
}

int main()
{
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
	
	//第18章 备忘录（Memento）模式:快照模式(Snapshot)，行为型模式。
	//（1）一个具体实现范例
	// 一般该模式还会引入一个 管理者（负责人）类，但是这并不是必须的。 FCareTaker

	//（2）引入备忘录（Memento）模式
	//定义：在不破坏封装性的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，
	  //这样以后就可以将该对象恢复到原先保存的状态
	//3种角色
	//a)Originator(原发器)：Fighter类。
	//b)Memento（备忘录）：FighterMemnto类。
	//c)CareTaker（负责人/管理者）：FCareTaker类。
	//备忘录真正的作用并不是保存数据，而是恢复数据。

	//说明：
	//a)快照——并不意味着所有玩家主角类的信息都要往备忘录中保存。
	//b)做快照并不要求玩家主角类中需要保存的字段都一一对应备忘录中相同的字段，备忘录中可以以
	   //内存流，字符串，编码（Hex编码、Base64编码）方式存储或者还原来自玩家主角类中的数据。
	   //数据序列化。
	//c)给玩家主角类做快照并不仅仅限于一次。FCareTaker2做多次快照。给了一个范例
	//d)friend
	//e)更适合保存原发器对象中的一部分（不是所有）内部状态，否则采用原型模式。
	//f)优点：方便回到一个特定的历史步骤。缺点：对资源的消耗。
	//g)完全存储。增量存储来应付频繁做快照。  完全存储和增量存储方式结合使用。Redis，RDB（完全备份），AOF（增量备份）。
	   //减少需要记录的数据。
	//h)应用场合：下棋悔棋，保存历史记录，做快照。
	//i)数据保存在内存中以及从内存中恢复数据。








	 





	/*
	_nmsp1::Fighter* p_fighter = new _nmsp1::Fighter(800, 200, 300);
	//(1)显示玩家主角在与BOSS战斗之前的信息
	p_fighter->displayInfo();

	//(2)为玩家主角类对象创建一个备忘录（其中保存了当前主角类对象中的必要信息）
	//_nmsp1::FighterMemento* p_fighterMemo = p_fighter->createMomento();
	_nmsp1::FCareTaker* pfcaretaker = new _nmsp1::FCareTaker(p_fighter->createMomento());

	//(3)玩家与BOSS开始战斗
	cout << "玩家主角与BOSS开始进行激烈的战斗-------" << endl;
	p_fighter->setToDead(); //玩家主角在与BOSS战斗中，生命值最终变成0而死亡（被BOSS击败）
	p_fighter->displayInfo(); //显示玩家主角在与BOSS战斗之后的信息

	//(4)因为在与BOSS战斗之前已经通过NPC保存了游戏进度，这里模拟载入游戏进度，恢复玩家主角类对象的数据，让其可以与BOSS再次战斗
	cout << "玩家主角通过备忘录恢复自己的信息------" << endl;
	//p_fighter->restoreMomento(p_fighterMemo);
	p_fighter->restoreMomento(pfcaretaker->getMemento());
	p_fighter->displayInfo(); //显示玩家主角通过备忘录恢复到战斗之前的信息

	//(5)释放资源
	//delete p_fighterMemo;
	delete pfcaretaker->getMemento();
	delete pfcaretaker; 
	delete p_fighter;
	*/

	_nmsp1::Fighter* p_fighter2 = new _nmsp1::Fighter(800, 200, 300);
	_nmsp1::FCareTaker2* pfcaretaker2 = new _nmsp1::FCareTaker2();
	pfcaretaker2->setMemento(p_fighter2->createMomento()); //做第一次快照，此快照玩家生命值为800。
	p_fighter2->setToDead(); //改变玩家主角的生命值
	pfcaretaker2->setMemento(p_fighter2->createMomento());//做第二次快照，此快照玩家生命值为0。
	p_fighter2->displayInfo(); //玩家主角生命值应为为0.
	cout << "----------------" << endl;
	//当前玩家生命值为0，恢复第一次快照，也就是恢复玩家生命值为800
	p_fighter2->restoreMomento(pfcaretaker2->getMemento(0));
	p_fighter2->displayInfo(); //玩家生命值应该恢复为800

	//释放资源
	delete p_fighter2;
	delete pfcaretaker2;



	return 0;
}

// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单

// 入门使用技巧: 
//   1. 使用解决方案资源管理器窗口添加/管理文件
//   2. 使用团队资源管理器窗口连接到源代码管理
//   3. 使用输出窗口查看生成输出和其他消息
//   4. 使用错误列表窗口查看错误
//   5. 转到“项目”>“添加新项”以创建新的代码文件，或转到“项目”>“添加现有项”以将现有代码文件添加到项目
//   6. 将来，若要再次打开此项目，请转到“文件”>“打开”>“项目”并选择 .sln 文件

